package 多线程初阶1;
import java.util.Scanner;
// volatile 关键字

/**
 * 计算机中一般理解成"可变的,容易改变的"
 * volatile的功能是保证内存的可见性,但是不能保证原子性
 * volatile是和编译器优化密切相关的东西
 * -- 编译器优化是一个相当复杂的问题...
 * 像下面不加volatile以及synchronized的代码,编译器读取flg频率很快,很容易发生编译器优化.可以sleep解决..'
 *
 * 一般来说,如果某个变量,在一个线程中读,一个线程中写,这个时候大概率是要使用volatile的
 *
 * volatile这里涉及到一个重要知识点,JMM(java memory model)内存模型
 * --
 */
//volatile 修饰的变量, 能够保证 "内存可见性".

/**
 * 下面的代码,如果没有volatile来修饰变量的话,会有编译器优化导致的问题.
 * 法①:
 * 使用volatile来禁止编译器进行刚才的优化.
 * volatile的用法比较单一,只能修饰一个具体的属性.
 * 此时代码中针对这个属性的读写操作就一定会涉及到内存操作了
 *
 * volatile不能保证原子性(串行)
 * 法②:
 * synchronized
 */
public class TestDemo4 {
    static class Counter{
        //一旦给这个flag加上volatile之后,后续的针对flag的读写操作,就都能保证一定是操作内存了
        volatile public int flag = 0;
    }
    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                //快速的循环读取flag的值(频繁的从内存读数据)
                //但是由于这里读的太快了,所以编译器直接进行了优化,并不会每次都从内存里读取flag的值
                //读了一次之后,后续都直接从CPU中来读取内存的值了(寄存器)
                //从内存读取是一件效率比较低的事情,如果直接从CPU中的寄存器读取,速度就会很块
//                --
                while(true){
                    //假设要执行一些操作
                    synchronized (counter){
                        //加上synchronized之后,此时的针对flag的操作,也会读写内存.
                        if(counter.flag != 0){
                            break;
                        }
                    }
                }
                System.out.println("循环结束");
            }
        };
        t1.start();
        Thread t2 = new Thread(){
            @Override
            public void run() {
//                让用户输入一个整数,用这个用户输入的值来替换counter.flag的值
                Scanner scanner = new Scanner(System.in);
                System.out.println("请输入一个整数:>");
                counter.flag = scanner.nextInt();
                //此时线程2是修改了内存里的值.
                //此时此刻,线程1感知不到flag内存对应的数据变化.
                //这个也是内存可见性的问题.(本质上都是编译器优化带来的问题)

            }
        };
        t2.start();
    }
}
